﻿#include "unit_test.h"
#include "print.h"
#include <stack>
#include <tuple>
#include <cassert>

namespace code_01_06
{
    // 为stack绑定一个名字
    // 为了简少代码使用继承，实际工作尽量少这么做
    class NamedStack : public std::stack<int>
    {
    public:
        NamedStack(const std::string& name)
            : name_(name)
        {}

        const std::string& name() const
        {
            return name_;
        }

    private:
        std::string name_;
    };

    namespace code_01_06_v1
    {
        class Hanoi
        {
        public:
            Hanoi(std::size_t level)
                : count_(0)
            {
                // 创建初始栈
                while (level > 0)
                {
                    left_.push(level);
                    --level;
                }
            }

            void solve()
            {
                leftToRight(left_.size());
                println("it will move", count_, "steps.");
            }

            void printSize()
            {
                println(left_.size(), mid_.size(), right_.size());
            }

            std::tuple<int, int, int> getSize() const
            {
                return std::make_tuple(left_.size(), mid_.size(), right_.size());
            }

            const std::stack<int>& getLeft() const
            {
                return left_;
            }

            const std::stack<int>& getRight() const
            {
                return right_;
            }

        private:
            void leftToRight(std::size_t n)
            {
                if (n > 0)
                {
                    leftToRight(n - 1);
                    leftToMiddle(1);
                    rightToLeft(n - 1);
                    middleToRight(1);
                    leftToRight(n - 1);
                }
            }

            void rightToLeft(std::size_t n)
            {
                if (n > 0)
                {
                    rightToLeft(n - 1);
                    rightToMiddle(1);
                    leftToRight(n - 1);
                    middleToLeft(1);
                    rightToLeft(n - 1);
                }
            }

            void leftToMiddle(std::size_t n)
            {
                if (n > 1)
                {
                    leftToRight(n - 1);
                }
                if (n > 0)
                {
                    transferOne(left_, mid_);
                }
                if (n > 1)
                {
                    rightToMiddle(n - 1);
                }
            }

            void middleToRight(std::size_t n)
            {
                if (n > 1)
                {
                    middleToLeft(n - 1);
                }
                if (n > 0)
                {
                    transferOne(mid_, right_);
                }
                if (n > 1)
                {
                    leftToRight(n - 1);
                }
            }

            void rightToMiddle(std::size_t n)
            {
                if (n > 1)
                {
                    rightToLeft(n - 1);
                }
                if (n > 0)
                {
                    transferOne(right_, mid_);
                }
                if (n > 0)
                {
                    leftToMiddle(n - 1);
                }
            }

            void middleToLeft(std::size_t n)
            {
                if (n > 1)
                {
                    middleToRight(n - 1);
                }
                if (n > 0)
                {
                    transferOne(mid_, left_);
                }
                if (n > 1)
                {
                    rightToLeft(n - 1);
                }
            }

            void transferOne(NamedStack& from, NamedStack& to)
            {
                assert(!from.empty());
                int top = from.top();
                from.pop();
                to.push(top);
                println("move", top, "from", from.name(), "to", to.name());
                ++count_;
            }

        private:
            std::size_t count_;
            NamedStack left_{ "left" };
            NamedStack mid_{ "mid" };
            NamedStack right_{ "right" };
        };

        TEST(HanoiMove)
        {
            std::stack<int> expected{ {3, 2, 1} };
            Hanoi hanoi(3);

            // 初始状态
            auto size = hanoi.getSize();
            ASSERT_EQ(true, size == std::make_tuple(3, 0, 0));
            ASSERT_EQ(true, expected == hanoi.getLeft());

            hanoi.solve();
            hanoi.printSize();

            // 结束状态
            size = hanoi.getSize();
            ASSERT_EQ(true, size == std::make_tuple(0, 0, 3));
            ASSERT_EQ(true, expected == hanoi.getRight());
        }
    }
}
